package com.bosicloud.cmb.ibp.aws.adapter.service.impl;

import com.alibaba.fastjson.JSON;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.cloudformation.AmazonCloudFormation;
import com.amazonaws.services.cloudformation.AmazonCloudFormationClientBuilder;
import com.amazonaws.services.cloudformation.model.*;
import com.amazonaws.services.sns.AmazonSNS;
import com.amazonaws.services.sns.AmazonSNSClientBuilder;
import com.amazonaws.services.sns.model.ListSubscriptionsByTopicResult;
import com.amazonaws.services.sns.model.SubscribeRequest;
import com.amazonaws.services.sns.model.SubscribeResult;
import com.amazonaws.services.sns.model.Subscription;
import com.bosicloud.cmb.ibp.QueueNames;
import com.bosicloud.cmb.ibp.aws.adapter.commom.dto.CreateAwsEnvDTO;
import com.bosicloud.cmb.ibp.aws.adapter.commom.dto.CreateCFStackResponseDTO;
import com.bosicloud.cmb.ibp.aws.adapter.commom.dto.RequestDescribeStackDTO;
import com.bosicloud.cmb.ibp.aws.adapter.commom.model.SQSInfo;
import com.bosicloud.cmb.ibp.aws.adapter.service.CloudFormationService;
import com.bosicloud.cmb.ibp.aws.adapter.utils.AWSUtils;
import com.bosicloud.cmb.ibp.commom.exception.BusinessException;
import com.bosicloud.cmb.ibp.commom.utils.MixUtils;
import com.bosicloud.cmb.ibp.commom.utils.RandomString;
import com.google.common.collect.ImmutableList;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.List;
import java.util.Optional;

/**
 * @author Jack Peng / ziliang.peng@bosicloud.com
 */
@Slf4j
@Service
public class CloudFormationServiceImpl implements CloudFormationService {

    @Autowired
    QueueNames queueNames;

    @Override
    public CreateCFStackResponseDTO createCFStack(CreateAwsEnvDTO awsEnv) {

        log.info("CreateCFStack: \n{}", JSON.toJSONString(awsEnv, true));

        /**
         * DEV INFO:
         * accessKey    AKIATMEE6DN74G55ME5D
         * secretKey    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
         * region       cn-north-1
         */
        Regions region = AWSUtils.checkRegion(awsEnv.getRegion().trim().toLowerCase());
        AWSUtils.chechEC2Type(awsEnv.getEcsInstanceType());

        AWSStaticCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(
                new BasicAWSCredentials(awsEnv.getAccessKey().trim(), awsEnv.getSecretKey().trim()));
        AmazonSNS sns = AmazonSNSClientBuilder.standard()
                .withCredentials(credentialsProvider)
                .withRegion(region)
                .build();

        final String topicArn = createSNSTopic(sns);
        final String subscribeArn = subscribeTopic(sns, topicArn);

        for (;;) {

            ListSubscriptionsByTopicResult subscriptionsByTopicResult = sns.listSubscriptionsByTopic(topicArn);
            if (subscriptionsByTopicResult.getSubscriptions().size() == 0) {
                log.info("订阅列表为空,等待重新获取.");
                MixUtils.sleepShort();
                continue;
            }

            boolean flag = false;
            for ( Subscription subscription : subscriptionsByTopicResult.getSubscriptions() ) {
                if(subscription.getSubscriptionArn().equalsIgnoreCase(subscribeArn)
                        && !"PendingConfirmation".equalsIgnoreCase(subscription.getSubscriptionArn())) {
                    flag = true;
                }
            }

            if(flag) {
                break;
            } else {
                log.info("订阅请求未确认,等待订阅确认: {}", subscribeArn);
                MixUtils.sleepShort();
            }
        }

        // 创建CF堆栈
        AmazonCloudFormation cloudFormation = AmazonCloudFormationClientBuilder.standard()
                .withCredentials(credentialsProvider)
                .withRegion(region)
                .build();
        ImmutableList<Parameter> parameters = ImmutableList.of(
                new Parameter().withParameterKey("UserData").withParameterValue(""),
                new Parameter().withParameterKey("EcsAmiId").withParameterValue(""),
                new Parameter().withParameterKey("AsgMaxSize").withParameterValue(awsEnv.getAsgMaxSize().toString()),
                new Parameter().withParameterKey("EcsClusterName").withParameterValue(awsEnv.getEcsClusterName()),
                new Parameter().withParameterKey("EcsInstanceType").withParameterValue(awsEnv.getEcsInstanceType()),
                new Parameter().withParameterKey("KeyName").withParameterValue(awsEnv.getKeyPairName()),
                // 安全组
                new Parameter().withParameterKey("EcsInstanceSecurityGroup").withParameterValue(awsEnv.getEcsSecurityGroup()),
                //new Parameter().withParameterKey("PubAlbSecurityGroup").withParameterValue(awsEnv.getPubSecurityGroup()),
                new Parameter().withParameterKey("PrivateAlbSecurityGroup").withParameterValue(awsEnv.getLbSecurityGroup()),
                // 网络
                new Parameter().withParameterKey("VpcId").withParameterValue(awsEnv.getVpnId()),
                new Parameter().withParameterKey("SubnetIds").withParameterValue(awsEnv.getSubnetIds()),
                //new Parameter().withParameterKey("PublicSubnetIds").withParameterValue(awsEnv.getPubSubnetIds()),
                new Parameter().withParameterKey("enablePublicApplicationLoadBalancer").withParameterValue("false"),
                // 角色
                new Parameter().withParameterKey("IamRoleECSServiceRole").withParameterValue(awsEnv.getEcsRole()),
                new Parameter().withParameterKey("IamRoleInstanceProfile").withParameterValue(awsEnv.getEc2Role()),
                // GIT
                new Parameter().withParameterKey("SpringCloudConfigServerDockerGitRepoUrl").withParameterValue(awsEnv.getConfigGitUrl()),
                new Parameter().withParameterKey("SpringCloudConfigServerDockerGitRepoUser").withParameterValue(awsEnv.getConfigGitUser()),
                new Parameter().withParameterKey("SpringCloudConfigServerDockerGitRepoPwd").withParameterValue(awsEnv.getConfigGitPassword()));

        /***
         new Parameter().withParameterKey("SpringCloudConfigServerDockerImageRepository").withParameterValue("")
         new Parameter().withParameterKey("SpringCloudHystrixDockerImageRepository").withParameterValue("")
         new Parameter().withParameterKey("SpringCloudZipkinDockerImageRepository").withParameterValue("")
        ***/

        String stackName = String.format("IBP-AutoCreateCluster-%s", RandomString.next(6));

        CreateStackRequest createStackRequest = new CreateStackRequest().
                withStackName(stackName).
                withClientRequestToken(awsEnv.getToken()).
                withNotificationARNs(topicArn).
                withParameters(parameters);

        Optional<String> s3template = Optional.ofNullable(awsEnv.getS3template());
        if (s3template.isPresent() && !"".equals(s3template.get().trim())) {
            log.debug("使用S3资源模板.");
            createStackRequest.setTemplateURL(s3template.get());
        } else {
            log.debug("未发现S3资源模板参数或路径格式错误,使用内置资源模板.");
            createStackRequest.setTemplateBody(readTemplate());
        }

        log.info("CreateStack Params \n{}", JSON.toJSONString(createStackRequest, true));

        try {
            cloudFormation.createStack(createStackRequest);
        } catch (AmazonCloudFormationException e) {
            throw new BusinessException(AWSUtils.awsException2ChineseAndPrint(e));
        }

        return CreateCFStackResponseDTO.builder().stackName(stackName).build();

    }

    private String readTemplate() {

        String template;
        try {
            template = IOUtils.toString(this.getClass().getResourceAsStream("/CloudFormation.yaml"));
        } catch (IOException e) {
            log.error("读取创建集群堆栈模板错误.", e);
            throw new BusinessException("读取创建集群堆栈模板错误.");
        }

        if (template != null && !"".equals(template.trim())) {
            return template;
        } else {
            throw new BusinessException("创建集群堆栈模板为空.");
        }
    }


    @Override
    public List<Output> descibeStack(RequestDescribeStackDTO describeStackDTO) {

        Regions region = AWSUtils.checkRegion(describeStackDTO.getRegion().trim().toLowerCase());

        AWSStaticCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(
                new BasicAWSCredentials(describeStackDTO.getAccessKey(), describeStackDTO.getSecretKey()));

        AmazonCloudFormation cloudFormation = AmazonCloudFormationClientBuilder.standard()
                .withCredentials(credentialsProvider)
                .withRegion(region)
                .build();

        DescribeStacksResult resp = cloudFormation.describeStacks(new DescribeStacksRequest()
                .withStackName(describeStackDTO.getStackName()));
        List<Stack> stacks = resp.getStacks();

        if (stacks != null && stacks.size() == 1) {
            return stacks.get(0).getOutputs();
        } else {
            throw new BusinessException("未发现CloudFormation Stack的输出信息.");
        }
    }

    private String createSNSTopic(AmazonSNS sns) {
        try {
            String topicArn = sns.createTopic("IBP-CFStackNotify-0").getTopicArn();
            log.info("Create SNS Topic Arn: {}", topicArn);
            return topicArn;
        } catch (Exception e) {
            throw new BusinessException(e.getMessage());
        }
    }

    private String subscribeTopic(AmazonSNS sns, String topicArn) {
        try {
            String subscriptionArn = SQSInfo.getARN(queueNames.getCfNotify());
            log.info("Created SNS {}", subscriptionArn);
            SubscribeResult subscribeResult = sns.subscribe(new SubscribeRequest().
                    withTopicArn(topicArn).
                    withProtocol("sqs").
                    withEndpoint(subscriptionArn).
                    withReturnSubscriptionArn(true));
            log.info("Subscribe SNS : Key = {}, SQS = {}, Result : \n{}",
                    queueNames.getCfNotify(),
                    subscriptionArn,
                    JSON.toJSONString(subscribeResult, true));
            return subscribeResult.getSubscriptionArn();
        } catch (Exception e) {
            throw new BusinessException(e.getMessage());
        }
    }
}
